Analyse comparative des élections européennes 2019 et 2024 en France
Dynamiques géographiques et politiques
Auteur : Bernier Aude
Date : Octobre 2025
Source : Ministère de l’Intérieur — Données publiques
🧭 Contexte du projet
Les élections européennes constituent un baromètre important de l’évolution politique et territoriale en France. Entre 2019 et 2024, la scène politique française a connu des recompositions notables, marquées par des dynamiques différenciées selon les territoires : métropole, Antilles, océan Indien, etc.
Ce projet vise à analyser et visualiser ces évolutions à travers une approche géographique et politique, en s’appuyant sur des données électorales officielles.
❓ Problématique
Cette question guide l’ensemble de l’analyse. Elle implique de croiser des dimensions spatiales (par régions et départements) et politiques (par listes et familles partisanes).
🎯 Objectifs du projet
🗺️ 1. Analyse géographique
- Identifier les zones de stabilité et de basculement électoral.
- Observer les contrastes entre régions métropolitaines et les DROM.
- Mettre en évidence les dynamiques de participation (inscrits, exprimés, abstention).
🏛️ 2. Analyse politique
- Étudier l’évolution du rapport de force entre les principales listes.
- Visualiser les transferts ou progressions de vote entre 2019 et 2024.
- Comprendre la recomposition du paysage partisan européen en France.
📊 Approche méthodologique
L’analyse repose sur :
- Le traitement de données électorales par Python (pandas, geopandas, plotly)
- Des visualisations interactives pour explorer les tendances régionales et nationales
- Une mise en perspective politique des résultats observés
🧩 Structure du notebook
- Chargement et nettoyage des données
- Analyse géographique (cartes et indicateurs régionaux)
- Analyse politique (voix par liste, évolutions, classements)
- Conclusion : synthèse et perspectives
📂 Jeu de données
Ces jeux de données proviennent du site officiel des données du gouvernement français :
Les identifiants ci-dessous correspondent aux différents fichiers récapitulant les résultats électoraux de chaque région et département. Ces identifiants ont été répartis dans deux dictionnaires afin de différencier les résultats de 2019 et de 2024.
# Dictionnaires identifiants -> nom_fichier
identifiants_2024 = {
"2690a1ed-13fb-4164-a006-2878000bf4c1": "departement",
'38e17714-1e07-46dc-96e1-71e752aa40d3': "region"
}
identifiants_2019 = {
'4a26fcae-494b-4ef6-82bb-49fdd32c8159': "departement",
'fd27d3c0-477d-4c87-ab38-8eb2543b5844': "region"
}
api_url = 'https://www.data.gouv.fr/api/1/datasets/r/'
📥 Chargement des données
Chargement des fichiers nécessaires via l’API dataset du gouvernement français.
Si besoin, les fichiers Excel des résultats des élections européennes sont également disponibles localement :
data/2019/ → résultats 2019
data/2024/ → résultats 2024
⚙️ Importations
Les bibliothèques essentielles sont importées pour le traitement, la manipulation et la visualisation des données électorales issues des scrutins européens.
- re — expressions régulières, nettoyage des chaînes
- json — lecture et écriture de données structurées (API, GeoJSON)
- unicodedata — normalisation et gestion des caractères accentués
- numpy — calculs numériques et manipulation de tableaux
- pandas — manipulation et transformation des données tabulaires
- geopandas — gestion et visualisation de données géographiques
- plotly.express — visualisations rapides et interactives
- plotly.graph_objects — personnalisation fine des graphiques
- plotly.subplots — création de figures multi-graphiques
# --- Importations ---
import re,json, unicodedata
import numpy as np
import pandas as pd
import geopandas as gpd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.io as pio
from IPython.display import display, HTML
display(HTML("<style>.plotly-graph-div { width: 100% !important; }</style>"))
pio.renderers.default = "notebook_connected"
🧩 Fonction de chargement des fichiers
La fonction charger_fichiers() permet d’automatiser le téléchargement et le chargement des fichiers électoraux à partir des identifiants fournis par l’API dataset du gouvernement français.
- Itère sur les identifiants et noms de fichiers fournis pour une année donnée.
- Construit l’URL complète de téléchargement depuis l’API.
- Charge chaque fichier au format Excel directement en DataFrame pandas.
- Stocke le résultat dans un dictionnaire avec un nom clé combinant l’année et le type de fichier.
# --- Fonction chargement des fichiers ---
def charger_fichiers(identifiants, annee):
dataframes = {}
for identifiant, nom in identifiants.items():
url = api_url + identifiant
print(f"Chargement {annee} - {nom} ...")
df = pd.read_excel(url)
dataframes[f"{annee}_{nom}"] = df
return dataframes
Chargement des tables 2024
Le chargement des DataFrames de l’année 2024 dure environ 3 minutes.
dfs_2024 = charger_fichiers(identifiants_2024, "2024")
Chargement 2024 - departement ... Chargement 2024 - region ...
Chargement des tables 2019
Le chargement des DataFrames de l’année 2019 dure environ 1 minute.
dfs_2019 = charger_fichiers(identifiants_2019, "2019")
Chargement 2019 - departement ... Chargement 2019 - region ...
🧮 Manipulation des données
Les données issues des élections européennes de 2019 présentent certaines incohérences structurelles, notamment :
- Des colonnes non nommées (Unnamed) ou incomplètes.
- Des valeurs inutiles ou redondantes dans plusieurs fichiers.
Afin de garantir la cohérence et la comparabilité des données entre 2019 et 2024, les algorithmes suivants effectuent un nettoyage automatique :
- Renommer toutes les colonnes Unnamed de manière explicite.
- Supprimer les colonnes vides, les doublons ou les champs non pertinents.
- Réorganiser la structure pour une uniformité entre les deux millésimes.
1. Initialisation des colonnes
Cette étape consiste à harmoniser la structure des colonnes entre les jeux de données 2019 et 2024. L’objectif est de rendre les noms de colonnes identiques afin de faciliter les opérations de comparaison et de concaténation.
- Définir un mapping entre les noms de colonnes bruts et les noms harmonisés.
- Renommer les colonnes dans chaque DataFrame selon ce dictionnaire de correspondance.
- Uniformiser les structures afin d’éviter toute erreur lors de la fusion des millésimes.
# --- Définition des colonnes utiles et harmonisation des noms ---
# --- Colonnes utiles par niveau géographique ---
colonnes_utiles = {
"departement": ["Code du département", "Libellé du département", "Code département", "Libellé département",
"Inscrits", "Votants", "Exprimés", "Abstentions", "Blancs", "Nuls"],
"region": ["Code de la région", "Libellé de la région", "Code région", "Libellé région",
"Inscrits", "Votants", "Exprimés", "Abstentions", "Blancs", "Nuls"]
}
# --- Mapping colonnes fixes harmonisées ---
mapping_colonnes = {
# Codes Département
"code_du_departement": "code_departement",
"code_departement": "code_departement",
# Codes Région
"code_de_la_region": "code_region",
"code_region": "code_region",
# Libellés Département
"libelle_du_departement": "nom_departement",
"libelle_departement": "nom_departement",
# Libellés Région
"libelle_de_la_region": "nom_region",
"libelle_region": "nom_region",
# Listes électorales
"libelle_abrege_de_liste": "liste_electorale",
"libelle_abrege_liste": "liste_electorale",
# Données électorales
"inscrits": "inscrits",
"votants": "votants",
"exprimes": "exprimes",
"exprimes_": "exprimes",
"abstentions": "abstentions",
"blancs": "blancs",
"nuls": "nuls"
}
2. Normalisation et harmonisation des colonnes
Cette étape vise à rendre la structure des données totalement homogène entre les jeux 2019 et 2024. Deux fonctions principales sont mises en œuvre :
- normaliser_nom_colonne() → uniformise la syntaxe des noms de colonnes (minuscules, suppression des accents, remplacement des espaces par underscores).
- nettoyer_colonnes() → nettoie et restructure les fichiers pour ne garder que les colonnes pertinentes à l’analyse électorale.
# --- Normalisation générique ---
def normaliser_nom_colonne(col):
"""
Normalise un nom de colonne :
- string
- minuscules
- suppression des accents
- espaces et ponctuations -> underscore
"""
col = str(col)
col = ''.join(c for c in unicodedata.normalize('NFD', col)
if unicodedata.category(c) != 'Mn') # enlever accents
col = col.lower()
col = re.sub(r'[^a-z0-9]+', '_', col).strip('_') # tout sauf alphanum -> _
return col
# Construire une version normalisée une seule fois
MAPPING_NORM = { normaliser_nom_colonne(k): v for k, v in mapping_colonnes.items() }
# --- Nettoyage des données ---
def nettoyer_colonnes(df, niveau, colonnes_utiles):
"""
Version optimisée mémoire :
- Gère 2019 (avec 'Unnamed') et 2024 (sans)
- Ne crée pas de colonnes drop inutiles
- Aligne liste_electorale_i / voix_i
- Normalise et mappe les noms de colonnes
"""
def startswith_percent(name: str) -> bool:
s = str(name).lstrip()
return s.startswith("%") or s.startswith("%") or s.startswith("‰")
dfcopy = df # évite la copie inutile
compteur = 1
# --- Cas 2019 : blocs 'Unnamed' ---
if any("Unnamed" in str(c) for c in dfcopy.columns):
cols = list(dfcopy.columns)
rename_map = {}
bloc_size = 7 # taille typique observée
start_bloc = None
compteur = 1
for idx, col in enumerate(cols):
if "Unnamed" in str(col):
if start_bloc is None:
start_bloc = idx
pos = ((idx - start_bloc) % bloc_size) + 1
if pos == 2:
rename_map[col] = f"liste_electorale_{compteur}"
elif pos == 5:
rename_map[col] = f"voix_{compteur}"
compteur += 1
else:
# colonnes inutiles : on ne les garde pas
continue
else:
rename_map[col] = col # colonne fixe
# On ne garde que les colonnes renommées existantes
dfcopy = dfcopy.rename(columns=rename_map)
dfcopy = dfcopy.loc[:, list(rename_map.values())]
# --- Cas 2024 : colonnes explicites ---
else:
cols = list(dfcopy.columns)
rename_map = {}
compteur = 1
i = 0
bloc_size = 3 # hypothèse : 3 colonnes "liste" pour 1 "voix"
while i < len(cols):
col = cols[i]
col_lower = str(col).lower()
# Début d'un bloc : colonne contenant "liste"
if "liste" in col_lower and not startswith_percent(col):
# On regroupe les 3 prochaines colonnes "liste"
bloc_listes = []
for j in range(i, min(i + bloc_size, len(cols))):
if "liste" in str(cols[j]).lower() and not startswith_percent(cols[j]):
bloc_listes.append(cols[j])
# On cherche la prochaine colonne "voix"
j = i + bloc_size
while j < len(cols) and "voix" not in str(cols[j]).lower():
j += 1
# Renommer la 1ère colonne du bloc en liste_electorale_i
if bloc_listes:
rename_map[bloc_listes[0]] = f"liste_electorale_{compteur}"
# Si une colonne "voix" existe, on la mappe aussi
if j < len(cols):
rename_map[cols[j]] = f"voix_{compteur}"
compteur += 1
i = j + 1
else:
i += 1
else:
i += 1
# Appliquer le mapping
dfcopy = dfcopy.rename(columns=rename_map)
# --- Normalisation et mapping ---
rename_map = {}
for c in dfcopy.columns:
if startswith_percent(c):
continue
norm = normaliser_nom_colonne(c)
rename_map[c] = MAPPING_NORM.get(norm, norm)
dfcopy = dfcopy.rename(columns=rename_map)
# --- Colonnes fixes ---
colonnes_fixes = [
MAPPING_NORM.get(normaliser_nom_colonne(x), normaliser_nom_colonne(x))
for x in colonnes_utiles[niveau]
]
# --- Colonnes finales à garder ---
colonnes_a_garder = [
c for c in dfcopy.columns
if (
c in colonnes_fixes
or re.fullmatch(r"liste_electorale_\d+", c)
or re.fullmatch(r"voix_\d+", c)
)
and not c.startswith("pct_")
]
# --- Alignement vérification ---
n_listes = len([c for c in colonnes_a_garder if c.startswith("liste_electorale_")])
n_voix = len([c for c in colonnes_a_garder if c.startswith("voix_")])
if n_listes != n_voix:
print(f"⚠️ {niveau}: désalignement détecté ({n_listes} listes / {n_voix} voix")
# --- Retour compact ---
df_final = dfcopy.loc[:, colonnes_a_garder].copy(deep=False)
return df_final
3. Données nettoyées
Les jeux de données 2019 et 2024 sont désormais harmonisés grâce aux fonctions de nettoyage et de normalisation précédemment définies. Cette étape applique la fonction nettoyer_colonnes() à l’ensemble des DataFrames et crée deux dictionnaires distincts : dfs_2019_clean et dfs_2024_clean.
- Nettoyer et structurer les données pour chaque niveau géographique (région, département).
- Aligner les noms de colonnes et le format des valeurs entre 2019 et 2024.
- Préparer les DataFrames pour les analyses comparatives et visuelles à venir.
Vérification :
dfs_2019_clean["2019_region"].head()
dfs_2024_clean["2024_region"].head()
# --- Dataframes nettoyées ---
dfs_2019_clean = {}
dfs_2024_clean = {}
# Mapping des niveaux pour correspondre aux clés de colonnes_utiles
map_niveaux = {
"region": "region",
"departement": "departement"
}
for nom, df in dfs_2019.items():
niveau_brut = nom.split("_")[1]
niveau = map_niveaux.get(niveau_brut, niveau_brut)
dfs_2019_clean[nom] = nettoyer_colonnes(df, niveau, colonnes_utiles)
for nom, df in dfs_2024.items():
niveau_brut = nom.split("_")[1]
niveau = map_niveaux.get(niveau_brut, niveau_brut)
dfs_2024_clean[nom] = nettoyer_colonnes(df, niveau, colonnes_utiles)
#dfs_2019_clean["2019_region"].head()
#dfs_2024_clean["2024_region"].head()
Jointures des bases de données
Cette étape permet de fusionner les jeux de données 2019 et 2024 afin de faciliter les comparaisons et les analyses visuelles. Les jointures sont réalisées sur les colonnes géographiques et électorales communes (code région, code département, etc.).
- Assembler les données de 2019 et 2024 dans une structure unifiée.
- Assurer la correspondance parfaite des entités géographiques.
- Préparer les DataFrames fusionnés pour les analyses graphiques et comparatives.
Mapping : abréviations et couleurs des listes électorales
Avant de procéder aux visualisations, un mapping est défini pour associer chaque liste électorale à :
- une abréviation courte (ex. RN, RE, LFI...) ;
- une couleur distinctive utilisée dans les graphiques pour assurer la lisibilité.
# --- Abréviations des listes & associations des listes par couleur ---
ABREVIATIONS_LISTES = {
# --- Gauche ---
"LA FRANCE INSOUMISE": "LFI",
"UNION POPULAIRE": "LFI",
"PARTI COMMUNISTE FRANÇAIS": "PCF",
"LUTTE OUVRIÈRE": "LO",
"PARTI SOCIALISTE": "PS",
"PLACE PUBLIQUE": "PS",
"NOUVELLE DONNE": "PS",
"EUROPE ÉCOLOGIE": "EELV",
"LES ÉCOLOGISTES": "EELV",
"GÉNÉRATION.S": "EELV",
"GAUCHE RÉPUBLICAINE ET SOCIALISTE": "GRS",
"PARTI RADICAL DE GAUCHE": "PRG",
"ENVIE D'EUROPE": "EEU",
"URGENCE ÉCOLOGIE": "UECO",
"À VOIX ÉGALES": "AVE",
"RÉVOLUTIONNAIRE": "REVG",
"RÉVOLUTION CITOYENNE": "RCIT",
# --- Centre / Majorité présidentielle ---
"RENAISSANCE": "RE",
"LA RÉPUBLIQUE EN MARCHE": "LREM",
"MOUVEMENT DÉMOCRATE": "MODEM",
"HORIZONS": "HOR",
"MAJORITÉ PRÉSIDENTIELLE": "RE",
"ENSEMBLE": "RE",
"ALLIANCE CENTRISTE": "RE",
"LES EUROPÉENS": "LEU",
"PARTI DES CITOYENS EUROPÉENS": "PCE",
"PARTI FÉDÉRALISTE EUROPÉEN": "PFE",
"PACE": "PACE",
"POUR L'EUROPE DES GENS": "PEG",
"UDLEF": "UDLEF",
"LENS": "RE", # Ensemble majorité présidentielle
# --- Droite ---
"LES RÉPUBLICAINS": "LR",
"LLR": "LR",
"UNION DROITE-CENTRE": "UDC",
"DEBOUT LA FRANCE": "DLF",
"LES PATRIOTES": "PAT",
"UNION DES DÉMOCRATES ET INDÉPENDANTS": "UDI",
"UNE FRANCE ROYALE": "UFR",
"LISTE DE LA RECONQUÊTE": "REC",
"LREC": "REC",
# --- Extrême droite ---
"RASSEMBLEMENT NATIONAL": "RN",
"LRN": "RN",
"FRONT NATIONAL": "RN",
"RECONQUÊTE": "REC",
"RECONQUÊTE !": "REC",
"PRENEZ LE POUVOIR": "RN",
# --- Autres / Divers ---
"PARTI ANIMALISTE": "ANI",
"ALLIANCE RURALE": "AR",
"LES OUBLIES DE L'EUROPE": "OUBL",
"LISTE CITOYENNE": "LCIT",
"NEUTRE ET ACTIF": "NEUT",
"LEXD": "EXD", # extrême droite
"LEXG": "EXG", # extrême gauche
"LVEC": "EELV", # variante écologiste
"LUG": "PS", # variante gauche unie
"LECO": "EELV",
"LCOM": "PCF",
"DIV": "DIV",
"LDIV": "DIV", # Divers
"AUT": "AUT",
"LDVD": "DVD", # Divers droite
"LDVG": "DVG", # Divers gauche
}
# Palette de couleurs pour chaque sigle de parti
COULEURS_LISTES = {
# --- Gauche / Extrême gauche ---
"LFI": "#B2182B", # Rouge foncé (La France Insoumise)
"PCF": "#D73027", # Rouge vif (Communistes)
"LO": "#EF8A62", # Rouge clair (Lutte Ouvrière)
"PS": "#F781BF", # Rose (Parti Socialiste)
"LUG": "#F781BF", # Gauche unie
"EELV": "#4DAF4A", # Vert (Écologistes)
"LVEC": "#4DAF4A",
"GRS": "#A6D96A", # Vert clair
"PRG": "#E41A1C", # Rouge clair
"DEC": "#C7E9C0", # Vert pâle (Décroissance)
"UECO": "#66C2A5", # Urgence écologie
"AVE": "#D9F0D3", # À voix égales
"REVG": "#FB8072", # Révolutionnaire
"RCIT": "#FBB4AE", # Révolution citoyenne
"EXG": "#FB9A99", # Extrême gauche
"DVG": "#F4A582", # Divers gauche (rose saumon)
# --- Centre / Majorité présidentielle ---
"RE": "#FFD92F", # Jaune (Renaissance)
"LREM": "#FFD92F",
"HOR": "#E6AB02", # Jaune-orangé (Horizons)
"MODEM": "#FDB863", # Orange clair
"ENS": "#E6AB02",
"LEU": "#FFFFB3",
"PACE": "#FFED6F",
"PCE": "#FEE391",
"PFE": "#FEE08B",
"UDLEF": "#FEE08B",
"PEG": "#FEE08B",
# --- Droite ---
"LR": "#377EB8", # Bleu (LR)
"LLR": "#377EB8",
"UDI": "#80B1D3", # Bleu clair (UDI)
"DLF": "#0571B0", # Bleu moyen (Debout la France)
"UDC": "#6BAED6", # Union Droite-Centre
"PAT": "#08519C", # Patriotes
"UFR": "#08306B", # France Royale (bleu royal)
"DVD": "#2171B5", # Divers droite (bleu acier)
"EXD": "#08306B", # Extrême droite modérée
# --- Extrême droite ---
"RN": "#08306B", # Bleu très foncé (Rassemblement National)
"REC": "#542788", # Violet foncé (Reconquête)
"LREC": "#542788",
"FREXIT": "#7B3294",
# --- Citoyens / Divers / Autres ---
"ANI": "#A6761D", # Brun (Animaliste)
"AR": "#B15928", # Brun rougeâtre (Alliance Rurale)
"OUBL": "#969696", # Gris moyen (Les Oubliés)
"LCIT": "#CCCCCC", # Liste Citoyenne
"NEUT": "#D9D9D9", # Neutre et Actif
"AJ": "#BDBDBD", # Alliance Jaune
"AE": "#BDBDBD", # Allons Enfants
"ESPPL": "#CAB2D6", # Europe au service des peuples
"ESP": "#CAB2D6",
"IC": "#E0E0E0", # Initiative Citoyenne
"LC": "#CCCCCC",
"PEU": "#B3B3B3",
"PIR": "#984EA3", # Parti Pirate
"PRC": "#FB8072",
"LFC": "#B2DF8A", # La France Citoyenne
"AUT": "#E0E0E0",
"DIV": "#D9D9D9",
}
1. Fonctions utilitaires
Ces fonctions centralisent les opérations récurrentes nécessaires à la manipulation, la transformation et la présentation des données. Elles seront utilisées tout au long du projet pour l’harmonisation des codes, la mise en forme des colonnes et la gestion des visualisations.
- harmoniser_codes() → unifie les codes géographiques (région, département, commune).
- transformer_voix_listes() → convertit les données en format long pour l’analyse par liste électorale.
- nettoyer_nom() → simplifie et normalise les noms (sans accents ni ponctuation).
- nom_court() → génère les abréviations standardisées des listes électorales.
- renommer_legende_auto() → reformate automatiquement les légendes et titres de colorbar des graphiques Plotly.
# --- Fonctions utilitaires ---
def harmoniser_codes(df):
"""
Harmonise les colonnes de codes administratifs :
- code_region
- code_departement
- code_commune
Les convertit en string si elles existent dans le DataFrame.
"""
colonnes_codes = [
"code_region", "code_departement", "code_commune",
]
for col in colonnes_codes:
if col in df.columns:
df[col] = df[col].astype(str)
return df
def transformer_voix_listes(df, id_vars):
"""
Met au format long (liste, voix) à partir des colonnes voix_i et liste_electorale_i.
id_vars: str ou liste de colonnes identifiantes (ex: 'nom_region_2024' ou ['code_region','nom_region_2024'])
"""
if isinstance(id_vars, str):
id_vars = [id_vars]
voix_cols = [c for c in df.columns if re.fullmatch(r"voix_\d+(_2019|_2024)?", c)]
if not voix_cols:
raise ValueError("Aucune colonne voix_i trouvée.")
# --- construit mapping voix_i_YYYY -> nom de la liste stable ---
mapping = {}
for c in voix_cols:
m = re.match(r"(voix_\d+)(_2019|_2024)?", c)
base, suffix = m.group(1), (m.group(2) or "")
idx = base.split("_")[1]
col_liste = f"liste_electorale_{idx}{suffix}"
if col_liste in df.columns:
# Cherche la valeur la plus fréquente (mode) pour ce nom de liste
lib = df[col_liste].dropna().mode()
mapping[c] = lib.iloc[0] if not lib.empty else base
else:
mapping[c] = base # fallback
# --- passage au format long ---
df_melt = df.melt(
id_vars=id_vars,
value_vars=voix_cols,
var_name="col_voix",
value_name="voix"
)
# --- associe la liste électorale ---
df_melt["liste"] = df_melt["col_voix"].map(mapping)
# --- extrait l’année ---
df_melt["annee"] = df_melt["col_voix"].str.extract(r"_(2019|2024)$")
return df_melt
def nettoyer_nom(nom):
"""Supprime accents, ponctuation et met en minuscules pour normaliser les comparaisons."""
if not isinstance(nom, str):
return ""
# enlever accents
nom = unicodedata.normalize("NFKD", nom).encode("ascii", errors="ignore").decode("utf-8")
# supprimer ponctuation et caractères spéciaux
nom = re.sub(r"[^a-z0-9 ]", " ", nom.lower())
# nettoyer les espaces multiples
return re.sub(r"\s+", " ", nom).strip()
def nom_court(nom_liste, n=22):
"""Retourne le sigle standardisé d'une liste électorale, avec correspondance robuste."""
nom_norm = nettoyer_nom(nom_liste)
for cle, abbr in ABREVIATIONS_LISTES.items():
if nettoyer_nom(cle) in nom_norm:
return abbr
# sinon, renvoie une version courte du nom (utile pour micro-listes)
return str(nom_liste)[:n]
def renommer_legende_auto(fig):
"""
Renomme automatiquement les légendes et les colorbars dans une figure Plotly Express ou une figure combinée.
Compatible avec :
- Graphiques multi-traces (bar, line, etc.)
- Choropleths simples
- Choropleths en sous-graphes (make_subplots)
"""
def format_label(name):
if not isinstance(name, str):
return name
base = re.sub(r"[_]+", " ", name).strip().capitalize()
# Détecte les années à la fin
match = re.match(r"(.*?)(\d{4})$", base)
if match:
texte, annee = match.groups()
texte = texte.strip().lower()
# Améliore la lisibilité avec "de" ou "d’"
if texte.startswith("taux "):
if texte.startswith("taux a"): # abstention
texte = "Taux d’abstention"
elif texte.startswith("taux e"): # exprimés
texte = "Taux d’exprimés"
elif texte.startswith("taux p"): # participation
texte = "Taux de participation"
else:
texte = "Taux de " + texte.split(" ", 1)[-1]
else:
texte = texte.capitalize()
return f"{texte} : {annee}"
return base.capitalize()
# --- Cas 1 : Graphiques multi-traces (barres, lignes, etc.)
if len(fig.data) > 1:
fig.for_each_trace(lambda t: t.update(name=format_label(t.name)))
# --- Cas 2 : Graphiques à 1 seule trace avec colorbar (choropleth simple)
elif len(fig.data) == 1 and hasattr(fig.data[0], "colorbar"):
trace = fig.data[0]
if trace.colorbar.title.text:
fig.update_coloraxes(colorbar_title_text=format_label(trace.colorbar.title.text))
else:
fig.update_coloraxes(colorbar_title_text=format_label(trace.name))
# --- Cas 3 : Sous-graphiques avec plusieurs choropleths
else:
for trace in fig.data:
if hasattr(trace, "colorbar") and trace.colorbar.title.text:
fig.update_traces(
selector={"coloraxis": trace.coloraxis},
colorbar_title_text=format_label(trace.colorbar.title.text)
)
return fig
2. Jointures 2019 ↔ 2024 + rattachements hiérarchiques
Cette étape combine les jeux de données 2019 et 2024 afin de permettre les comparaisons temporelles.
Les opérations consistent à harmoniser les codes administratifs, renommer les colonnes pour chaque année et effectuer les jointures entre bases régionales et départementales.
- Uniformiser les clés géographiques (code_region,code_departement).
- Ajouter des suffixes _2019 et _2024 pour distinguer les variables.
- Réaliser les jointures pour obtenir des structures comparables par entité géographique.
# --- Chargement & manipulation des bases nettoyées + jointures 2019-2024 ---
# --- Sélection des niveaux géographiques
df_reg_2019 = dfs_2019_clean["2019_region"]
df_reg_2024 = dfs_2024_clean["2024_region"]
df_dep_2019 = dfs_2019_clean["2019_departement"]
df_dep_2024 = dfs_2024_clean["2024_departement"]
# --- Harmoniser les codes administratifs avant jointures
df_reg_2019 = harmoniser_codes(df_reg_2019)
df_reg_2024 = harmoniser_codes(df_reg_2024)
df_dep_2019 = harmoniser_codes(df_dep_2019)
df_dep_2024 = harmoniser_codes(df_dep_2024)
# --- Ajouter un suffixe "_2024" à toutes les colonnes (sauf la clé)
df_reg_2024 = df_reg_2024.rename(columns={
c: f"{c}_2024" for c in df_reg_2024.columns if c != "code_region"})
# Idem pour 2019 si besoin
df_reg_2019 = df_reg_2019.rename(columns={
c: f"{c}_2019" for c in df_reg_2019.columns if c != "code_region"})
df_dep_2024 = df_dep_2024.rename(columns={
c: f"{c}_2024" for c in df_dep_2024.columns if c != "code_departement"})
# Idem pour 2019 si besoin
df_dep_2019 = df_dep_2019.rename(columns={
c: f"{c}_2019" for c in df_dep_2019.columns if c != "code_departement"})
# --- Jointure pour effectuer des comparaisons entre 2019 et 2024
# Région
df_reg = df_reg_2019.merge(
df_reg_2024,
on="code_region",
suffixes=("_2019", "_2024"))
# Département
df_dep = df_dep_2019.merge(
df_dep_2024,
on="code_departement",
suffixes=("_2019", "_2024"))
# df_reg.head()
# df_dep.head()
3. Géométries (GeoJSON) & fusion spatiale
Les jeux de données géographiques sont importés à partir des fichiers GeoJSON disponibles sur le dépôt FranceGEOJSON. Cette étape permet de rattacher les informations électorales aux géométries régionales et départementales, pour produire les futures cartes interactives.
- Importer les fichiers géographiques des régions et départements.
- Harmoniser les codes administratifs entre les DataFrames et les GeoDataFrames.
- Effectuer la fusion spatiale pour relier les géométries aux données électorales.
# --- URLs GEOJSON & merges des géométries
# --- URLs FranceGEOJSON (régions + départements avec DROM)
url_regions = "https://france-geojson.gregoiredavid.fr/repo/regions.geojson"
url_deps = "https://france-geojson.gregoiredavid.fr/repo/departements.geojson"
gdf_regions = gpd.read_file(url_regions)
gdf_deps = gpd.read_file(url_deps)
# Harmoniser types des codes (string)
gdf_regions["code"] = gdf_regions["code"].astype(str)
df_reg["code_region"] = df_reg["code_region"].astype(str)
gdf_deps["code"] = gdf_deps["code"].astype(str)
df_dep["code_departement"] = df_dep["code_departement"].astype(str)
# --- Correction des codes DOM nécessaire
map_dom = {
"01": "1", "02": "2", "03": "3",
"04": "4", "06": "6"
}
gdf_regions["code"] = gdf_regions["code"].replace(map_dom)
# Merge avec géométries
gdf_reg_final = gdf_regions.merge(df_reg, left_on="code", right_on="code_region", how="left")
gdf_dep_final = gdf_deps.merge(df_dep, left_on="code", right_on="code_departement", how="left")
# Vérification des bases pour graphiques
print("Bases prêtes :")
print("Régions :", gdf_reg_final.shape)
print("Départements :", gdf_dep_final.shape)
Bases prêtes : Régions : (18, 160) Départements : (96, 160)
📊 Data Visualisation
1. Chargement et préparation des données
Cette première étape prépare les bases de données nettoyées et harmonisées pour la phase de visualisation. Elle consiste à copier les GeoDataFrames finaux et à calculer des indicateurs électoraux communs tels que les taux de participation et d’abstention pour 2019 et 2024.
- Charger les GeoDataFrames finaux (gdf_reg_final et gdf_dep_final).
- Créer de nouveaux indicateurs : taux de participation et taux d’abstention.
- Préparer les données pour l’affichage graphique (cartes et bar charts comparatifs).
# --- Calculs des indicateurs ---
# Charger les bases nettoyées et harmonisées
df_reg = gdf_reg_final.copy()
df_dep = gdf_dep_final.copy()
# Calcul d'indicateurs communs
for df in [df_reg, df_dep]:
df["taux_participation_2024"] = df["votants_2024"] / df["inscrits_2024"] * 100
df["taux_participation_2019"] = df["votants_2019"] / df["inscrits_2019"] * 100
df["taux_abstention_2024"] = 100 - df["taux_participation_2024"]
df["taux_abstention_2019"] = 100 - df["taux_participation_2019"]
2. Analyse de la participation électorale
Cette section examine l’évolution du corps électoral et du taux de participation entre les élections européennes de 2019 et 2024. Elle met en évidence les dynamiques régionales et départementales à travers plusieurs visualisations interactives.
- Comparer le nombre d’inscrits et de suffrages exprimés entre 2019 et 2024.
- Étudier l’évolution du taux de participation à différents niveaux géographiques.
- Identifier les territoires où la participation électorale a le plus évolué.
Liste des graphiques réalisés
- Comparaison 2019 vs 2024 du corps électoral et des suffrages exprimés par région
- Graphique en barres comparatives pour chaque région.
- Permet d’observer la stabilité du nombre d’inscrits et l’évolution des exprimés.
- Évolution du taux de participation par région (2019 vs 2024)
- Visualisation en barres verticales avec codes couleur par année.
- Mise en évidence des hausses ou baisses de participation régionales.
- Taux de participation par département (2019 vs 2024)
- Carte choroplèthe interactive (Plotly) illustrant les écarts de participation.
- Possibilité de survoler les départements pour afficher les valeurs précises.
- Focalisation : taux de participation du département d’Île-de-France
- Zoom spécifique sur la région francilienne.
- Analyse du différentiel de participation entre 2019 et 2024.
- Utilisation de plotly.express pour les comparaisons régionales (bar charts).
- Exploration des taux via des cartes choropleth basées sur gdf_reg_final et gdf_dep_final.
- Application de la fonction renommer_legende_auto() pour une présentation homogène.
📊 Comparaison 2019 vs 2024 du corps électoral et des suffrages exprimés par région
Ce graphique compare, pour chaque région française, le nombre d’inscrits et le nombre de suffrages exprimés entre les élections européennes de 2019 et 2024. Il met en évidence les dynamiques de participation et la stabilité du corps électoral à l’échelle régionale.
- Comparer le volume du corps électoral (inscrits) entre 2019 et 2024.
- Visualiser la progression ou la baisse des suffrages exprimés selon les régions.
- Identifier les zones de forte ou faible mobilisation électorale.
# --- Graphique : Corps électoral et suffrages exprimés par région (2019 vs 2024) ---
# --- Tri des régions selon les exprimés 2024 ---
ordre_regions = (
df_reg.groupby("nom_region_2024")["exprimes_2024"]
.sum()
.sort_values(ascending=False)
.index.tolist()
)
# --- Figure ---
fig = go.Figure()
# --- Barres 2019
# # Exprimés (partie inférieure)
fig.add_trace(go.Bar(
x=df_reg["nom_region_2024"],
y=df_reg["exprimes_2019"],
name="Exprimés 2019",
marker_color="rgba(255,140,0,0.9)",
offsetgroup="2019" ))
# Inscrits (complément par-dessus)
fig.add_trace(go.Bar(
x=df_reg["nom_region_2024"],
y=df_reg["inscrits_2019"] - df_reg["exprimes_2019"],
base=df_reg["exprimes_2019"],
name="Inscrits 2019",
marker_color="rgba(100,149,237,0.4)",
offsetgroup="2019" ))
# --- Barres 2024
# Exprimés (partie inférieure)
fig.add_trace(go.Bar(
x=df_reg["nom_region_2024"],
y=df_reg["exprimes_2024"],
name="Exprimés 2024",
marker_color="rgba(255,69,0,0.9)",
offsetgroup="2024" ))
# Inscrits (complément par-dessus)
fig.add_trace(go.Bar(
x=df_reg["nom_region_2024"],
y=df_reg["inscrits_2024"] - df_reg["exprimes_2024"],
base=df_reg["exprimes_2024"],
name="Inscrits 2024",
marker_color="rgba(30,144,255,0.4)",
offsetgroup="2024" ))
# --- Mise en page esthétique ---
fig.update_layout(
template="plotly_white",
barmode="group",
bargap=0.25,
height=650,
width=1000,
title=dict(
text="<b>Corps électoral et suffrages exprimés par région</b><br>"
"<span style='font-size:15px; color:#555;'>Comparaison entre les élections européennes 2019 et 2024</span>",
x=0.5,
y=0.92,
xanchor="center",
font=dict(size=20, family="Arial, Open Sans", color="#222")
),
xaxis=dict(
categoryorder="array",
categoryarray=ordre_regions,
title="<b>Région</b>",
tickangle=45,
tickfont=dict(size=11)
),
yaxis=dict(
title="<b>Nombre de personnes</b>",
tickformat=",.0f",
tickfont=dict(size=13),
gridcolor="rgba(200,200,200,0.3)"
),
legend=dict(
title="<b>Catégories</b>",
orientation="v",
x=1.02,
y=0.5,
xanchor="left",
yanchor="middle",
bgcolor="rgba(255,255,255,0.8)",
bordercolor="rgba(0,0,0,0.2)",
borderwidth=1,
font=dict(size=12),
title_font=dict(size=13)
),
margin=dict(l=60, r=120, t=120, b=80),
paper_bgcolor="#F9FAFB",
plot_bgcolor="#F9FAFB",
)
# --- Affichage ---
fig.show()
Analyse du graphique : évolution de la participation électorale entre 2019 et 2024
Le graphique met en évidence une stabilité globale du nombre d’inscrits entre les élections européennes de 2019 et de 2024 : le corps électoral reste relativement constant dans l’ensemble des régions françaises.
En revanche, le nombre de suffrages exprimés augmente légèrement dans la majorité des régions, notamment dans les plus peuplées comme l’Île-de-France, Auvergne–Rhône-Alpes ou encore l’Occitanie, où la différence de participation entre les deux scrutins est visible. De manière générale, l’ensemble des régions métropolitaines témoignent d’une mobilisation électorale légèrement supérieure en 2024.
- Stabilité du corps électoral → peu de variation du nombre d’inscrits entre 2019 et 2024.
- Légère hausse des exprimés dans la plupart des grandes régions métropolitaines.
- Participation plus faible dans les DROM (Guadeloupe, Martinique, Guyane, Mayotte, La Réunion).
📈 Évolution du taux de participation par région (2019 vs 2024)
Cette visualisation met en évidence les variations du taux de participation entre les élections européennes de 2019 et 2024 pour chaque région française. Elle permet d’identifier les territoires ayant connu une hausse ou une baisse notable de la mobilisation électorale.
- Comparer les taux de participation régionaux entre 2019 et 2024.
- Visualiser les écarts de mobilisation sur l’ensemble du territoire.
- Repérer les régions les plus dynamiques électoralement.
# --- Définitions des zones géographiquess ---
codes_metropole = ["11","24","27","28","32","44","52","53","75","76","84","93","94"]
codes_antilles = ["1", "2", "3"] # Guadeloupe, Martinique, Guyane
codes_indien = ["4", "6"] # Réunion, Mayotte
# --- Échelle commune ---
def echelle_commune(df):
vmin = float(min(df["taux_participation_2019"].min(),
df["taux_participation_2024"].min()))
vmax = float(max(df["taux_participation_2019"].max(),
df["taux_participation_2024"].max()))
return vmin, vmax
vmin = echelle_commune(df_reg)[0]
vmax = echelle_commune(df_reg)[1]
# --- Initialisation des zones ---
zones = [
("metropole", 1, {"lat": 46.6, "lon": 2.5}),
("antilles", 2, {"lat": 12, "lon": -61}),
("indien", 3, {"lat": -18, "lon": 52}),
]
def filtre_zone(gdf, zone, code):
if zone == "metropole":
return gdf[gdf[code].isin(codes_metropole)]
elif zone == "antilles":
return gdf[gdf[code].isin(codes_antilles)]
elif zone == "indien":
return gdf[gdf[code].isin(codes_indien)]
else:
return gdf
def assurer_CRS(df):
""" S'assurer du CRS lon/lat (WGS84) + """
if getattr(df, "crs", None) is not None:
try:
if df.crs.to_epsg() != 4326:
df = df.to_crs(epsg=4326)
except Exception:
df = df.to_crs(epsg=4326)
# --- Identifiants stables & GeoJSON ---
df = df.reset_index(drop=True)
geojson = json.loads(df.to_json())
return df, geojson
df = assurer_CRS(df_reg)[0]
geojson_reg = assurer_CRS(df_reg)[1]
# --- Graphique : Taux de participation par région ---
# --- Figure 3x2 ---
fig = make_subplots(
rows=3, cols=2,
subplot_titles=[
"Métropole 2019", "Métropole 2024",
"Antilles-Guyane 2019", "Antilles-Guyane 2024",
"Océan Indien 2019", "Océan Indien 2024"
],
specs=[
[{"type": "choropleth"}, {"type": "choropleth"}]]*3,
horizontal_spacing=0.03,
vertical_spacing=0.07
)
# --- Boucle de génération des 6 cartes ---
for row, (zone, _, center) in enumerate(zones, start=1):
for col, annee in enumerate(["2019", "2024"], start=1):
gdf_zone = filtre_zone(df_reg, zone, "code")
# Si aucune donnée (sécurité)
if gdf_zone.empty:
continue
fig.add_trace(
go.Choropleth(
geojson=gdf_zone.__geo_interface__,
locations=gdf_zone["code"],
z=gdf_zone[f"taux_participation_{annee}"],
featureidkey="properties.code",
coloraxis="coloraxis",
text=gdf_zone[f"nom_region_{annee}"],
hovertemplate="<b>%{text}</b><br>Taux de participation: %{z:.1f}%<extra></extra>",
),
row=row, col=col
)
# --- Lier chaque carte à un “geo” indépendant ---
for idx, geo in enumerate(["geo1","geo2","geo3","geo4","geo5","geo6"]):
fig.data[idx].geo = geo
# --- Mise en forme et centrage précis ---
fig.update_layout(
dragmode=False, # désactive le drag
coloraxis=dict(
colorscale="Viridis",
cmin=vmin,
cmax=vmax,
colorbar=dict(
title=dict(
text="<b>Taux de participation (%)</b>",
font=dict(size=13, color="#333")
),
orientation="v", # verticale
title_side="top",
thickness=16,
len=0.75,
x=1.02, # à droite des cartes
y=0.45,
yanchor="middle",
tickfont=dict(size=11),
ticksuffix="%"
)
),
# Métropole
geo=dict(center={"lat": 46.6, "lon": 2.5},projection_type="mercator",lonaxis_range=[-6, 10],lataxis_range=[41, 52],
showcoastlines=True,showcountries=True,fitbounds="locations", bgcolor="rgba(0,0,0,0)"),
geo2=dict(center={"lat": 46.6, "lon": 2.5},projection_type="mercator",lonaxis_range=[-6, 10],lataxis_range=[41, 52],
showcoastlines=True,showcountries=True,fitbounds="locations", bgcolor="rgba(0,0,0,0)"),
# Antilles-Guyane
geo3=dict(center={"lat": 12, "lon": -61},projection_type="mercator",lonaxis_range=[-67, -49],lataxis_range=[4, 18],
showcoastlines=True,showcountries=True,fitbounds="locations", bgcolor="rgba(0,0,0,0)"),
geo4=dict(center={"lat": 12, "lon": -61},projection_type="mercator",lonaxis_range=[-67, -49],lataxis_range=[4, 18],
showcoastlines=True,showcountries=True,fitbounds="locations", bgcolor="rgba(0,0,0,0)"),
# Océan Indien
geo5=dict(center={"lat": -18, "lon": 52},projection_type="mercator",lonaxis_range=[43, 58], lataxis_range=[-23, -10],
showcoastlines=True,showcountries=True,fitbounds="locations", bgcolor="rgba(0,0,0,0)"),
geo6=dict(center={"lat": -18, "lon": 52},projection_type="mercator",lonaxis_range=[43, 58], lataxis_range=[-23, -10],
showcoastlines=True,showcountries=True,fitbounds="locations", bgcolor="rgba(0,0,0,0)"),
# --- Titre principal ---
title=dict(
text="<b>Taux de participation par région</b><br>"
"<span style='font-size:15px; color:#555;'>Comparaison entre 2019 et 2024 — Métropole et DROM</span>",
x=0.5,
y=0.97,
xanchor="center",
yanchor="top",
font=dict(size=20, family="Arial, Open Sans", color="#222")
),
# --- Style général ---
width=850,
height=1050,
margin=dict(l=40, r=60, t=120, b=50),
paper_bgcolor="#F9FAFB",
plot_bgcolor="#F9FAFB",
font=dict(family="Open Sans, Arial", size=13, color="#333"),
)
# --- Supprimer les axes visibles ---
for axis in fig.layout:
if axis.startswith("xaxis") or axis.startswith("yaxis"):
fig.layout[axis].visible = False
fig.show()
Analyse du graphique : géographie du taux de participation en 2019 et 2024
Les cartes mettent en évidence une stabilité générale des taux de participation entre les élections européennes de 2019 et 2024, tout en révélant des écarts territoriaux marqués.
En métropole, la participation demeure globalement comprise entre 45 % et 55 %, avec des taux légèrement plus élevés dans le Grand Ouest (Bretagne, Pays de la Loire) et le Sud-Ouest. À l’inverse, des régions comme le Grand Est ou la Provence-Alpes-Côte d’Azur affichent des niveaux un peu plus faibles. Cette constance relative traduit une mobilisation modérée mais stable des électeurs métropolitains.
On remarque tout de même une légère augmentation des participations entre 2019 et 2024, dans l'ensemble des régions métroppolitaines, mis à part la Corse.
- La France métropolitaine conserve une participation moyenne, entre 45 et 55 %.
- Des contrastes régionaux existent : l’Ouest reste plus mobilisé que le Sud-Est.
- La stabilité temporelle traduit un ancrage électoral européen relativement constant.
Dans les DROM, la situation est radicalement différente. Les cartes des Antilles-Guyane et de l’océan Indien affichent des taux de participation très faibles, souvent inférieurs à 30 %, et parfois autour de 10 % en Guyane.
Entre 2019 et 2024, ces territoires ne montrent aucune amélioration significative, confirmant une fracture civique persistante entre la métropole et les outre-mer. Cette faible participation s’explique à la fois par un désintérêt vis-à-vis des enjeux européens et par des facteurs socio-économiques et politiques propres à ces régions.
- Une France métropolitaine modérément mobilisée, où le vote européen conserve un ancrage citoyen.
- Une France ultramarine largement détachée du scrutin, marquée par un désintérêt électorale européen.
Cette géographie différenciée de la participation montre une division politique observée en 2024, et souligne un investissement électoral croissant entre les régions et à la dynamique européenne et ceux qui s’en sentent exclus.
🗺️ Taux de participation par département : 2019 vs 2024
Cette double carte choroplèthe illustre les taux de participation électorale lors des élections européennes de 2019 et 2024, à l’échelle départementale. Elle met en lumière les différences territoriales de mobilisation électorale à travers l’ensemble du territoire français.
- Visualiser la répartition géographique du taux de participation en 2019 et 2024.
- Mettre en évidence les départements les plus et les moins mobilisés.
- Comparer visuellement la progression ou la stabilité de la participation selon les territoires.
# --- Initialisation des dataframes et geojson ---
df_dep = assurer_CRS(df_dep)[0]
geojson_dep = assurer_CRS(df_dep)[1]
locations_ids = df_dep["nom_departement_2024"] # correspondra au "id" du GeoJSON
# --- Graphique : Taux de participation par département ---
# --- Sous-graphes ---
fig = make_subplots(
rows=1, cols=2,
subplot_titles=["2019", "2024"],
specs=[[{"type": "choropleth"}, {"type": "choropleth"}]],
horizontal_spacing=0.03
)
# --- Infobulle personnalisée ---
hover_tpl = "<b>%{customdata}</b><br>Taux de participation : <b>%{z:.1f}%</b><extra></extra>"
# --- Traces ---
fig.add_trace(
go.Choropleth(
geojson=geojson_dep,
locations=locations_ids,
z=df_dep["taux_participation_2019"],
featureidkey="properties.nom_departement_2024",
coloraxis="coloraxis",
customdata=df_dep["nom_departement_2024"],
hovertemplate=hover_tpl,
marker_line_width=0.5,
marker_line_color="white"
),
row=1, col=1
)
fig.add_trace(
go.Choropleth(
geojson=geojson_dep,
locations=locations_ids,
z=df_dep["taux_participation_2024"],
featureidkey="properties.nom_departement_2024",
coloraxis="coloraxis",
customdata=df_dep["nom_departement_2024"],
hovertemplate=hover_tpl,
marker_line_width=0.5,
marker_line_color="white"
),
row=1, col=2
)
# --- Lier chaque trace à sa géo ---
fig.data[0].geo = "geo"
fig.data[1].geo = "geo2"
# --- Mise en forme générale ---
fig.update_layout(
template="plotly_white",
dragmode=False,
title=dict(
text="<b>Taux de participation par département</b><br>"
"<span style='font-size:15px; color:#555;'>Comparaison des élections européennes 2019 et 2024</span>",
x=0.5,
y=0.93,
xanchor="center",
yanchor="top",
font=dict(size=20, family="Arial, Open Sans")
),
height=800,
width=1100,
margin=dict(l=40, r=60, t=120, b=60),
paper_bgcolor="#F9FAFB",
plot_bgcolor="#F9FAFB",
# --- Échelle de couleurs Viridis (titre horizontal centré) ---
coloraxis=dict(
colorscale="Viridis",
cmin=vmin,
cmax=vmax,
colorbar=dict(
title=dict(
text="<b>Taux de participation (%)</b>",
font=dict(size=13, color="#333")
),
orientation="h", # Barre horizontale
title_side="top", # Titre au-dessus
x=0.5, # Centrée
y=-0.12, # Sous les cartes
xanchor="center",
yanchor="top",
len=0.65, # Largeur relative
thickness=15,
tickfont=dict(size=11),
ticksuffix="%"
)
),
# --- Focus sur la métropole ---
geo=dict(
projection_type="mercator",
lonaxis_range=[-6, 10],
lataxis_range=[41, 52],
showcoastlines=False,
showcountries=True,
showframe=False,
bgcolor="rgba(0,0,0,0)"
),
geo2=dict(
projection_type="mercator",
lonaxis_range=[-6, 10],
lataxis_range=[41, 52],
showcoastlines=False,
showcountries=True,
showframe=False,
bgcolor="rgba(0,0,0,0)"
),
font=dict(family="Open Sans, Arial", size=13, color="#333"),
)
# --- Suppression axes ---
fig.update_xaxes(visible=False)
fig.update_yaxes(visible=False)
# --- Sous-titres “2019” / “2024” plus proches et lisibles ---
for ann in fig['layout']['annotations']:
ann['y'] = 0.93
ann['font'] = dict(size=15, color="#222", family="Arial")
fig.show()
Analyse du graphique : évolution du taux de participation par département (2019–2024)
La comparaison des taux de participation départementaux entre 2019 et 2024 révèle une progression générale mais inégale de la mobilisation électorale. Sur la carte de 2024, les nuances de jaune plus soutenu, traduisent une hausse des taux de participation notamment dans plusieurs départements de l’Ouest (Bretagne) et du Sud-Ouest (Occitanie) , où la participation dépasse fréquemment 55 %. Cette évolution contraste avec la carte de 2019, plus uniformément teintée de couleurs intermédiaires, signe d’une mobilisation plus modérée.
- Le Sud-Ouest (Nouvelle-Aquitaine, Occitanie) se distingue par une forte participation, souvent supérieure à 55 %, confirmant la vitalité civique de ces territoires, déjà observée lors de précédents scrutins.
- Le Nord-Est (Hauts-de-France, Grand Est) présente des taux nettement plus faibles, parfois proches de 45 %, traduisant une désaffection persistante dans les zones industrielles et rurales touchées par le désengagement politique.
- La Bretagne demeure une région particulièrement mobilisée, avec des niveaux de participation supérieurs à ceux de la Normandie, confirmant un ancrage civique historique plus solide à l’Ouest.
- Les régions du Sud-Est (Provence-Alpes-Côte d’Azur, Corse) ne montrent aucune amélioration notable entre 2019 et 2024 : la participation y reste inférieure à la moyenne nationale, marquée par un vote européen peu mobilisateur.
Cette hétérogénéité traduit une fracture civique territorialisée, où les zones rurales et périurbaines, notamment dans le Nord-Est et le Sud-Est, se mobilisent nettement moins que les régions de l’Ouest et du Sud-Ouest plus participatives. L’Île-de-France et la Normandie se situent dans une position intermédiaire, affichant une participation proche de la moyenne nationale sans réelle progression entre 2019 et 2024.
Les DROM confirment les tendances observées précédemment : la Corse et La Réunion présentent des taux particulièrement faibles, entre 35 % et 40 %. Ce déintérêt durable souligne la distance persistante vis-à-vis du scrutin européen dans ces territoires. Elle reflète des dynamiques socio-économiques et politiques propres aux espaces ultramarins, souvent marqués par un sentiment d’exclusion du débat européen.
🏙️ Taux de participation du département d’Île-de-France : 2019 vs 2024
Ce graphique met en évidence l’évolution du taux de participation électorale dans les départements de la région Île-de-France entre les élections européennes de 2019 et de 2024. Il permet d’analyser les différences internes au territoire francilien, marqué par une forte densité urbaine et une diversité sociologique prononcée.
- Comparer les taux de participation départementaux franciliens entre 2019 et 2024.
- Identifier les départements les plus et les moins mobilisés.
- Observer les écarts entre zones urbaines et périurbaines de la région capitale.
# --- Graphique : Taux de participation — Île-de-France (2019 vs 2024) ---
# --- Sélection des départements d’Île-de-France ---
idf_codes = ["75", "77", "78", "91", "92", "93", "94", "95"]
df_idf = df_dep[df_dep["code_departement"].isin(idf_codes)].copy()
# --- Création du GeoJSON limité à l’Île-de-France ---
df_idf = df_idf.reset_index(drop=True)
df = assurer_CRS(df_idf)[0]
geojson_idf = assurer_CRS(df_idf)[1]
locations_ids = df_idf["nom_departement_2024"]
# --- Figure : 1 ligne, 2 colonnes ---
fig = make_subplots(
rows=1, cols=2,
subplot_titles=["2019", "2024"],
specs=[[{"type": "choropleth"}, {"type": "choropleth"}]],
horizontal_spacing=0.03
)
# --- Infobulle personnalisée ---
hover_tpl = "<b>%{customdata}</b><br>Taux de participation : <b>%{z:.1f}%</b><extra></extra>"
# --- Ajout des 2 cartes ---
fig.add_trace(
go.Choropleth(
geojson=geojson_idf,
locations=locations_ids,
z=df_idf["taux_participation_2019"],
featureidkey="properties.nom_departement_2024",
coloraxis="coloraxis",
customdata=df_idf["nom_departement_2024"],
hovertemplate=hover_tpl,
marker_line_width=0.6,
marker_line_color="white"
),
row=1, col=1
)
fig.add_trace(
go.Choropleth(
geojson=geojson_idf,
locations=locations_ids,
z=df_idf["taux_participation_2024"],
featureidkey="properties.nom_departement_2024",
coloraxis="coloraxis",
customdata=df_idf["nom_departement_2024"],
hovertemplate=hover_tpl,
marker_line_width=0.6,
marker_line_color="white"
),
row=1, col=2
)
# --- Lier chaque trace à sa géo ---
fig.data[0].geo = "geo"
fig.data[1].geo = "geo2"
# --- Mise en page esthétique ---
fig.update_layout(
template="plotly_white",
dragmode=False,
title=dict(
text="<b>Taux de participation — Île-de-France</b><br>"
"<span style='font-size:15px; color:#555;'>Comparaison des élections européennes 2019 et 2024</span>",
x=0.5,
y=0.93,
xanchor="center",
yanchor="top",
font=dict(size=20, family="Arial, Open Sans")
),
height=700,
width=1050,
margin=dict(l=40, r=60, t=120, b=40),
paper_bgcolor="#F9FAFB",
plot_bgcolor="#F9FAFB",
# --- Échelle de couleurs Viridis + titre horizontal ---
coloraxis=dict(
colorscale="Viridis",
cmin=vmin,
cmax=vmax,
colorbar=dict(
title=dict(
text="<b>Taux de participation (%)</b>",
font=dict(size=13, color="#333")
),
orientation="h", # <-- titre horizontal
title_side="top", # titre au-dessus de la barre
x=0.5, # centré horizontalement
y=-0.15, # sous les cartes
xanchor="center",
yanchor="top",
len=0.6, # longueur relative
thickness=15,
tickfont=dict(size=11),
ticksuffix="%"
)
),
# --- Cartes synchronisées et zoomées sur l’Île-de-France ---
geo=dict(
projection_type="mercator",
center=dict(lon=2.35, lat=48.85),
lonaxis_range=[1.5, 3.5],
lataxis_range=[48, 49.5],
showcoastlines=False,
showcountries=False,
showframe=False,
bgcolor="rgba(0,0,0,0)"
),
geo2=dict(
projection_type="mercator",
center=dict(lon=2.35, lat=48.85),
lonaxis_range=[1.5, 3.5],
lataxis_range=[48, 49.5],
showcoastlines=False,
showcountries=False,
showframe=False,
bgcolor="rgba(0,0,0,0)"
),
font=dict(family="Open Sans, Arial", size=13, color="#333"),
)
# --- Nettoyage des axes ---
fig.update_xaxes(visible=False)
fig.update_yaxes(visible=False)
# --- Sous-titres (“2019”, “2024”) ---
for ann in fig['layout']['annotations']:
ann['y'] = 0.93
ann['font'] = dict(size=15, color="#222", family="Arial")
fig.show()
Analyse du graphique : évolution du taux de participation en Île-de-France (2019–2024)
La comparaison des taux de participation départementaux en Île-de-France entre 2019 et 2024 révèle une légère hausse de la mobilisation électorale, mais aussi la persistance de fortes disparités internes au sein de la région capitale. Les données montrent que les départements les plus centraux et urbanisés, notamment Paris et les Hauts-de-Seine affichent les taux de participation les plus élevés, dépassant 55 % en 2024, soit une progression notable par rapport à 2019.
- Paris et les Hauts-de-Seine : participation élevée (>55 %) en 2024, hausse significative depuis 2019.
- Le Val-de-Marne, l'Essonne et les Yvelines : niveaux intermédiaires autour de 50 %.
- La Seine-Saint-Denis et le Val-d’Oise: participation plus faible (< 45%), marquant un désengagement durable.
Ces contrastes illustrent une fracture civique intra-régionale, où la géographie montre directement les différences socio-économiques entre l'Ouest de l'Île-de-France, Paris inclus et l'Est de l'Île-de-France.
Les zones les plus aisées et centrales se montrent davantage investies dans le scrutin européen, tandis que les espaces périurbains et populaires présentent une abstention structurelle plus forte. Cette géographie électorale reflète les différences sociales et territoriales propre à la région francilienne : proximité des institutions, niveau d’éducation, stabilité socio-professionnelle et sentiment d’intégration à l’Europe.
Les départements de la grande couronne, souvent éloignés des centres décisionnels et marqués par des conditions socio-économiques plus fragiles, restent moins mobilisés, traduisant une fracture civique persistante.
🧩 Transformation des données : mise au format long
Avant toute visualisation, les données issues des élections de 2019 et 2024 sont converties dans un format long adapté aux analyses comparatives et aux graphiques Plotly. Cette étape permet d’unifier les structures de données des régions et des départements, en rassemblant les voix de chaque liste dans une table unique, où chaque ligne correspond à une combinaison (territoire, liste, année, voix).
- Transformer les bases régionales et départementales en tables longues.
- Associer à chaque ligne une année (2019 ou 2024), une liste électorale et un nombre de voix.
- Ajouter une abréviation standardisée pour chaque liste (ex. RN, LFI, LR, etc.) afin de faciliter la lecture graphique.
# --- Format long & sigles politiques ---
# --- Table longue régions 2024 & 2019
df_melt_reg24 = transformer_voix_listes(
df_reg,
id_vars=["code_region","nom_region_2024"]
)
df_melt_reg19 = transformer_voix_listes(
df_reg,
id_vars=["code_region","nom_region_2024"]
)
# --- Table longue sur 2019 & 2024 des régions
df_long_reg = transformer_voix_listes(
df_reg,
id_vars=["code_region", "nom_region_2019", "nom_region_2024"]
)
df_long_reg = df_long_reg[df_long_reg["annee"].isin(["2019", "2024"])].copy()
# Nom abrégé (sigle politique)
df_long_reg["liste abrégée"] = df_long_reg["liste"].map(lambda x: nom_court(x, 22))
# --- Table longue sur 2019 & 2024 des départements
df_long_dep = transformer_voix_listes(
df_dep,
id_vars=["code_departement", "nom_departement_2019", "nom_departement_2024"]
)
df_long_dep = df_long_dep[df_long_dep["annee"].isin(["2019", "2024"])].copy()
# Nom abrégé (sigle politique)
df_long_dep["liste abrégée"] = df_long_dep["liste"].map(lambda x: nom_court(x, 22))
🗳️ Répartition des voix par liste — Élections européennes 2019 vs 2024
Ce graphique présente la répartition des suffrages exprimés entre les principales listes électorales françaises lors des élections européennes de 2019 et 2024. Il permet de visualiser les évolutions des principales forces politiques au niveau national, ainsi que les dynamiques de progression ou de recul de chaque groupe.
- Comparer les parts de voix obtenues par les principales listes entre 2019 et 2024.
- Identifier les gains et pertes électoraux des grandes formations politiques.
- Visualiser la recomposition des forces électorales françaises à travers le scrutin européen.
# --- Calcul des principales listes electorales ---
# --- Top listes calculé sur les DEUX années (pour cohérence des couleurs)
TOP_N = 4
top_2019 = (
df_long_reg[df_long_reg["annee"] == "2019"]
.groupby("liste", as_index=False)["voix"]
.sum()
.sort_values("voix", ascending=False)
.head(TOP_N)["liste"]
)
top_2024 = (
df_long_reg[df_long_reg["annee"] == "2024"]
.groupby("liste", as_index=False)["voix"]
.sum()
.sort_values("voix", ascending=False)
.head(TOP_N)["liste"]
)
top_listes = set(top_2019).union(set(top_2024))
# --- Graphique : Votes exprimés par liste électorale (2019 vs 2024) ---
# --- Harmonisation des colonnes ---
df_long_reg["liste_top"] = np.where(df_long_reg["liste"].isin(top_listes), df_long_reg["liste"], "Autres")
df_long_reg["liste abrégée"] = np.where(
df_long_reg["liste"].isin(top_listes), df_long_reg["liste abrégée"], "Autres"
)
# --- Nettoyage : suppression espaces/casse ---
df_long_reg["liste abrégée"] = df_long_reg["liste abrégée"].str.strip().str.upper()
# --- Ordre de la légende ---
order_legende = (
df_long_reg.groupby("liste abrégée")["voix"]
.sum()
.sort_values(ascending=False)
.index.tolist()
)
if "AUTRES" in order_legende:
order_legende = [x for x in order_legende if x != "AUTRES"] + ["AUTRES"]
# --- Ordre des régions ---
ordre_regions = (
df_long_reg.groupby("nom_region_2024")["voix"]
.sum()
.sort_values(ascending=False)
.index.tolist()
)
# --- Couleurs cohérentes ---
couleurs_listes = {k: v for k, v in COULEURS_LISTES.items() if k in order_legende}
couleurs_listes["AUTRES"] = "#BDBDBD"
# --- Figure ---
fig = px.bar(
df_long_reg,
x="nom_region_2024",
y="voix",
color="liste abrégée",
facet_col="annee",
category_orders={
"nom_region_2024": ordre_regions,
"liste abrégée": order_legende,
"annee": ["2019", "2024"],
},
color_discrete_map=couleurs_listes,
barmode="stack",
title="<b>Répartition et évolution des principales forces politiques par région</b><br>"
"<span style='font-size:15px; color:#555;'>Comparaison des élections européennes 2019 et 2024</span>",
labels={
"voix": "Nombre de voix exprimées",
"nom_region_2024": "Région",
"liste abrégée": "Listes",
},
)
# --- Nettoyer les titres de facettes ---
fig.for_each_annotation(lambda a: a.update(text=a.text.replace("annee=", "")))
# --- Axes et fond ---
fig.update_yaxes(
title_text="Nombre de voix exprimées",
showgrid=True,
gridcolor="rgba(200,200,200,0.3)",
zeroline=False,
)
fig.update_xaxes(
title_text="",
tickangle=45,
showgrid=False,
)
# --- Mise en page ---
fig.update_layout(
template="plotly_white",
height=650,
width=1100,
bargap=0.15,
margin=dict(t=120, b=60, l=60, r=200),
plot_bgcolor="#F9FAFB",
paper_bgcolor="#F9FAFB",
font=dict(family="Open Sans, Arial", size=13, color="#333"),
legend=dict(
title="<b>Listes électorales</b>",
orientation="v", # verticale
x=1.03, # à droite
xanchor="left",
y=0.5,
yanchor="middle",
bgcolor="rgba(255,255,255,0.8)",
bordercolor="rgba(0,0,0,0.1)",
borderwidth=1,
itemsizing="constant",
font=dict(size=12),
),
)
# --- Sous-titres facettes harmonisés ---
for ann in fig.layout.annotations:
ann.font = dict(size=14, color="#222", family="Arial")
ann.y += 0.02
fig.show()
Analyse du graphique : répartition des voix par région — 2019 vs 2024
Le graphique met en évidence une évolution nette de la hiérarchie électorale entre 2019 et 2024, marquée par un augmentation importante du Rassemblement national (RN) et un recul significatif de la majorité présidentielle (RE). Cette recomposition confirme la fracture croissante du paysage politique français à l’échelle régionale.
En 2019, le RN dominait déjà dans plusieurs régions, en particulier dans le Nord, l’Est et le Sud, mais la différence demeurait encore limitée face à Renaissance. En 2024, l’écart se creuse nettement dans presque toutes les régions, faisant du RN la première force politique sur la quasi-totalité du territoire métropolitain. Seules quelques régions urbaines et universitaires (Île-de-France, Auvergne-Rhône-Alpes, Occitanie) présentent une résistance électorale plus marquée.
La liste Renaissance, majoritaire en 2019 dans plusieurs régions (Île-de-France, Bretagne, Pays de la Loire), enregistre en 2024 un recul marqué, dans l'ensemble des régions. Cette diminution reflète un affaiblissement du soutien électoral à la majorité présidentielle, marqué par un affaiblissement du lien électora des classes moyennes et rurales envers le projet européen défendu par le centre.
>La gauche retrouve une certaine vitalité en 2024, mais demeure fragmentée :
- PS → regain notable dans l'ensemble des régions comparés à 2019.
- LFI → progression dans les grandes métropoles et les départements populaires.
- EELV → enregistre un recul important après son pic de 2019, potentiellement lié à un essouflement relatif de la dynamique du parti écologiste.
Cette diversité des dynamiques témoigne d’un rééquilibrage à gauche, mais sans réelle unité électorale.
La comparaison 2019 et 2024 révèle une disparité du paysage politique autour de plusieurs sujets :
Ces évolutions révèlent une fracture territoriale croissante : la France électorale se divise entre les périphéries, qui votent massivement RN en quête de protection, et les grandes villes, plus favorables au projet européen et à l'ouverture.
🏆 Identification des listes arrivées en tête — Régions et Départements (2019 & 2024)
Cette étape vise à déterminer, pour chaque région et chaque département, la liste électorale arrivée en tête lors des élections européennes de 2019 et de 2024. L’objectif est d’identifier les dynamiques territoriales dominantes et de repérer les zones de force des principaux partis.
- Déterminer automatiquement la liste gagnante (ayant obtenu le plus de voix) pour chaque région et département.
- Créer deux tableaux récapitulatifs distincts : un pour les régions (df_tetes) et un pour les départements (df_tetes_dep).
- Préparer ces données pour les futures cartes électorales et visualisations comparatives.
# --- Liste électorale gagnante ---
# Gagnant par région / année
idx_max_reg = df_long_reg.groupby(["annee", "code_region"])["voix"].idxmax()
df_tetes = (df_long_reg.loc[idx_max_reg, ["annee", "code_region", "nom_region_2024", "liste abrégée", "liste","voix"]]
.sort_values(["annee", "nom_region_2024"])
.rename(columns={"liste abrégée": "liste_en_tete", "voix": "voix_en_tete"}))
# Gagnant par département / année
idx_max_dep = df_long_dep.groupby(["annee", "code_departement"])["voix"].idxmax()
df_tetes_dep = (df_long_dep.loc[idx_max_dep, ["annee", "code_departement", "nom_departement_2024", "liste abrégée", "liste","voix"]]
.sort_values(["annee", "nom_departement_2024"])
.rename(columns={"liste abrégée": "liste_en_tete", "voix": "voix_en_tete"}))
🗺️ Cartes des listes arrivées en tête par région — 2019 vs 2024
Cette visualisation présente la carte électorale régionale de la France pour les élections européennes de 2019 et 2024. Les six cartes indiquent, pour chaque territoire, la liste arrivée en première position — en métropole, aux Antilles-Guyane et dans l'océan Indien. Cela permet d'avoir une vision complète de l'évolution politique entre les deux scrutins.
- Comparer la répartition territoriale des listes gagnantes entre 2019 et 2024.
- Visualiser bascule politique dans plusieurs régions métropolitaines.
- Identifier la diversité électorale des régions.
# --- Fonction utilitaire + initialisation couleur/parti ---
# --- Fonction pour filtrer le GeoDataFrame et générer un GeoJSON Plotly-compatible ---
def geojson_par_zone_annee(gdf_geo, df_attr, zone, annee):
# géométrie filtrée (par codes DOM/Métropole)
g = filtre_zone(gdf_geo, zone, "code_region").copy()
# attributs pour l'année
attrs = (
df_attr[df_attr["annee"] == annee]
.loc[:, ["code_region", "liste_en_tete", "nom_region_2024"]]
.drop_duplicates("code_region")
)
# jointure géo + attributs
g = g.merge(attrs, on="code_region", how="left")
# retourne un FeatureCollection utilisable par Plotly
return json.loads(g.to_json())
# --- Créer la palette à partir de COULEURS_LISTES ---
partis = list(COULEURS_LISTES.keys())
palette = [COULEURS_LISTES[p] for p in partis]
color_scale = [[i / (len(palette) - 1), palette[i]] for i in range(len(palette))]
# --- Graphique : listes arrivées en têtes par région ---
# --- Figure 3x2 ---
fig = make_subplots(
rows=3, cols=2,
subplot_titles=[
"Métropole 2019", "Métropole 2024",
"Antilles-Guyane 2019", "Antilles-Guyane 2024",
"Océan Indien 2019", "Océan Indien 2024",
],
specs=[[{"type": "choropleth"}, {"type": "choropleth"}]] * 3,
horizontal_spacing=0.03,
vertical_spacing=0.07,
)
# --- Générer tous les GeoJSONs possibles ---
geojson_dict = {}
for zone in ["metropole", "antilles", "indien"]:
for annee in ["2019", "2024"]:
geojson_dict[(zone, annee)] = geojson_par_zone_annee(df_reg, df_tetes, zone, annee)
# --- Boucle zones × années ---
for row, (zone, _, center) in enumerate(zones, start=1):
for col, annee in enumerate(["2019", "2024"], start=1):
gj = geojson_dict[(zone, annee)]
if not isinstance(gj, dict) or "features" not in gj or len(gj["features"]) == 0:
continue
# Sous-ensemble attributaire aligné sur le GeoJSON
codes_geo = {f["properties"]["code_region"] for f in gj["features"]}
df_zone = filtre_zone(df_tetes, zone, "code_region")
df_zone = df_zone[(df_zone["annee"] == annee) & (df_zone["code_region"].isin(codes_geo))].copy()
if df_zone.empty:
continue
z_values = df_zone["liste_en_tete"].map(
lambda x: partis.index(x) if x in partis else len(palette) - 1
)
# Infobulle plus élégante
customdata = df_zone[["nom_region_2024", "liste_en_tete", "voix_en_tete"]].values
hovertemplate = (
"<b>%{customdata[0]}</b><br>"
"Liste en tête : <b>%{customdata[1]}</b><br>"
"Voix : %{customdata[2]:,.0f}<extra></extra>"
)
fig.add_trace(
go.Choropleth(
geojson=gj,
locations=df_zone["code_region"],
z=z_values,
featureidkey="properties.code_region",
colorscale=color_scale,
zmin=0, zmax=len(palette) - 1,
showscale=False,
customdata=customdata,
hovertemplate=hovertemplate,
marker_line_width=0.5,
marker_line_color="white",
),
row=row, col=col,
)
# --- Lier les géos ---
geo_names = [f"geo{i}" for i in range(1, 7)]
for idx, geo in enumerate(geo_names[:len(fig.data)]):
fig.data[idx].geo = geo
# --- Mise en page et centrage ---
fig.update_layout(
template="plotly_white",
dragmode=False,
title=dict(
text="<b>Listes arrivées en tête par région</b><br>"
"<span style='font-size:15px; color:#555;'>Comparaison des élections européennes 2019 et 2024</span>",
x=0.5, y=0.95,
xanchor="center", yanchor="top",
font=dict(size=20, family="Arial, Open Sans")
),
width=950,
height=1150,
margin=dict(l=40, r=40, t=150, b=40),
paper_bgcolor="#F9FAFB",
plot_bgcolor="#F9FAFB",
font=dict(size=20, family="Arial, Open Sans"),
# --- Zones géographiques ---
geo=dict(center={"lat": 46.6, "lon": 2.5}, projection_type="mercator",
lonaxis_range=[-6, 10], lataxis_range=[41, 52], showcoastlines=True, showcountries=True,fitbounds="locations",bgcolor="rgba(0,0,0,0)"),
geo2=dict(center={"lat": 46.6, "lon": 2.5}, projection_type="mercator",
lonaxis_range=[-6, 10], lataxis_range=[41, 52], showcoastlines=True, showcountries=True,fitbounds="locations",bgcolor="rgba(0,0,0,0)"),
geo3=dict(center={"lat": 12, "lon": -61}, projection_type="mercator",
lonaxis_range=[-67, -49], lataxis_range=[4, 18],showcoastlines=True, showcountries=True,fitbounds="locations",bgcolor="rgba(0,0,0,0)"),
geo4=dict(center={"lat": 12, "lon": -61}, projection_type="mercator",
lonaxis_range=[-67, -49], lataxis_range=[4, 18],showcoastlines=True, showcountries=True, fitbounds="locations",bgcolor="rgba(0,0,0,0)"),
geo5=dict(center={"lat": -18, "lon": 52},projection_type="mercator",
lonaxis_range=[43, 58],lataxis_range=[-23, -10], showcoastlines=True,showcountries=True,fitbounds="locations",bgcolor="rgba(0,0,0,0)"),
geo6=dict(center={"lat": -18, "lon": 52},projection_type="mercator",
lonaxis_range=[43, 58],lataxis_range=[-23, -10],showcoastlines=True,showcountries=True, fitbounds="locations",bgcolor="rgba(0,0,0,0)"),
)
# --- Nettoyage visuel ---
for axis in fig.layout:
if axis.startswith("xaxis") or axis.startswith("yaxis"):
fig.layout[axis].visible = False
# on garde les 15 partis les plus représentés pour la lisibilité
top_partis = df_tetes["liste_en_tete"].value_counts().head(5).index
for i, parti in enumerate(top_partis):
fig.add_trace(go.Scatter(
x=[None], y=[None],
mode="markers",
marker=dict(size=10, color=COULEURS_LISTES.get(parti, "#BDBDBD")),
legendgroup=parti,
showlegend=True,
name=parti
))
fig.update_layout(
legend=dict(
title_text="<b>Listes électorales</b>",
orientation="h",
x=0.5,
xanchor="center",
y=1.02,
yanchor="bottom",
bgcolor="rgba(255,255,255,0.6)",
bordercolor="rgba(0,0,0,0.1)",
borderwidth=1,
font=dict(size=12),
title_font=dict(size=13)
)
)
fig.show()
Analyse du graphique : listes arrivées en tête par région — 2019 vs 2024
Ces cartes permettent de visualiser la distribution territoriale du vote majoritaire lors des élections européennes de 2019 et 2024, en distinguant les régions métropolitaines et DROM. L’évolution est nette : entre les deux scrutins, le Rassemblement national (RN), représenté en bleu foncé, a considérablement renforcé sa position, passant d'une domination partielle à une domination quasi totale sur le territoire national.
- 🔵 Le Rassemblement national (RN) arrive en tête dans une large partie du territoire (Sud, Est et Nord), confirmant son ancrage dans les espaces ruraux et périurbains.
- 🟡 La liste Renaissance (RE), en jaune, s’impose dans plusieurs régions métropolitaines, notamment dans l’Ouest (Bretagne, Pays de la Loire) et le Centre.
La carte de 2019 révèle ainsi une fragmentation électorale, où le vote RN s’oppose à un pôle centriste encore solide.
En 2024, la situation change radicalement : le RN s’impose dans la totalité des régions métropolitaines, y compris celles où il était auparavant minoritaire. Cette progression traduit une extension sociologique et géographique de son électorat, au-delà de ses bastions traditionnels du Nord-Est et du Midi.
Le vote centriste et pro-européen de Renaissance s’effondre, dans l'ensemble des régions, tandis que la gauche demeure marginale, concentrée dans les grandes villes seulement.
- En 2019, certaines zones (notamment Antilles et Guyane) affichaient encore une pluralité électorale avec des succès ponctuels de Renaissance.
- En 2024, le RN domine également dans ces territoires, à l’exception de la Martinique, où La France insoumise (LFI), en rouge, arrive en tête, signe d’une spécificité politique marquée par une défiance vis-à-vis des partis de droite.
- Dans l’océan Indien (La Réunion, Mayotte), le RN conserve son implantation stable sur les deux scrutins, traduisant la durabilité de sa présence électorale dans les territoires éloignés de la métropole.
Ces cartes illustrent une reconfiguration majeure du paysage politique français :
- La France, autrefois partagée entre plusieurs blocs régionaux, est désormais unifiée autour d’un vote RN majoritaire.
- Le centre pro-européen s'efface, et la gauche reste fragmentée.
- Le vote RN devient un vote national transversal, touchant aussi bien les zones rurales que périurbaines, et jusqu’à certains territoires d'outre-mer.
Ce basculement marque une rupture politique et territoriale inédite dans l'histoire électorale européenne récente. La France électorale se divise désormais selon un fossé profond opposant centres et périphéries, mêlant enjeux sociaux et politiques.
🗺️ Cartes des listes arrivées en tête par département — France métropolitaine (2019 vs 2024)
Cette visualisation présente la carte électorale départementale de la France métropolitaine pour les élections européennes de 2019 et 2024. Chaque département est coloré selon la liste politique arrivée en tête, offrant une lecture immédiate de la dynamique électorale sur l’ensemble du territoire.
- Comparer la répartition géographique des listes gagnantes entre les deux scrutins.
- Identifier les zones de continuité ou de bascule politique dans les départements métropolitains.
- Mettre en évidence la polarisation territoriale des principaux partis politiques.
Cette carte a été réalisée à partir des résultats agrégés par département, à l’aide d’un choroplèthe interactif développé avec Plotly. Elle illustre la stabilité ou la recomposition des bastions électoraux en France métropolitaine entre 2019 et 2024.
# --- Graphique : Têtes de liste par département (France métropolitaine) ---
# --- Retirer les DROM du DataFrame ---
codes_drom = ["971", "972", "973", "974", "975", "976"]
df_tetes_dep = df_tetes_dep[~df_tetes_dep["code_departement"].isin(codes_drom)]
# --- Nettoyage du GeoJSON (supprime les DROM) ---
gj_filtered = {
"type": "FeatureCollection",
"features": [
f for f in geojson_dep["features"]
if f["properties"].get("code_departement") not in codes_drom
]
}
partis = list(COULEURS_LISTES.keys())
palette = [COULEURS_LISTES[p] for p in partis]
color_scale = [[i / (len(palette) - 1), palette[i]] for i in range(len(palette))]
# --- Création du subplot ---
fig = make_subplots(
rows=1, cols=2,
subplot_titles=("2019", "2024"),
specs=[[{"type": "choropleth"}, {"type": "choropleth"}]],
)
# --- Boucle sur les années ---
for col, annee in enumerate(["2019", "2024"], start=1):
df_zone = df_tetes_dep[df_tetes_dep["annee"] == annee].copy()
if df_zone.empty:
continue
z_values = df_zone["liste_en_tete"].map(
lambda x: partis.index(x) if x in partis else len(palette) - 1
)
# --- Hover plus élégant ---
customdata = df_zone[["nom_departement_2024", "liste_en_tete", "voix_en_tete"]].values
hovertemplate = (
"<b>%{customdata[0]}</b><br>"
"Liste en tête : <b>%{customdata[1]}</b><br>"
"Voix : %{customdata[2]:,.0f}<extra></extra>"
)
fig.add_trace(
go.Choropleth(
geojson=gj_filtered,
locations=df_zone["code_departement"],
z=z_values,
featureidkey="properties.code_departement",
colorscale=color_scale,
zmin=0, zmax=len(palette) - 1,
showscale=False,
customdata=customdata,
hovertemplate=hovertemplate,
marker_line_width=0.5,
marker_line_color="white",
),
row=1, col=col,
)
# --- Lier les géos ---
if len(fig.data) > 0:
fig.data[0].geo = "geo"
if len(fig.data) > 1:
fig.data[1].geo = "geo2"
# --- Mise en page centrée sur la métropole + style pro ---
fig.update_layout(
template="plotly_white",
dragmode=False,
title=dict(
text="<b>Listes arrivées en tête par département — France métropolitaine</b><br>"
"<span style='font-size:15px; color:#555;'>Comparaison des élections européennes 2019 et 2024</span>",
x=0.5,
y=0.93,
xanchor='center',
yanchor='top',
font=dict(size=20, family="Arial")
),
height=750,
width=1100,
margin=dict(l=40, r=40, t=100, b=10),
geo=dict(
center={"lat": 46.6, "lon": 2.5},
projection_type="mercator",
lonaxis_range=[-6, 10],
lataxis_range=[41, 52],
showcoastlines=True,
showcountries=True,
fitbounds="locations"
),
geo2=dict(
center={"lat": 46.6, "lon": 2.5},
projection_type="mercator",
lonaxis_range=[-6, 10],
lataxis_range=[41, 52],
showcoastlines=True,
showcountries=True,
fitbounds="locations"
),
paper_bgcolor="#F9FAFB",
plot_bgcolor="#F9FAFB",
font=dict(family="Open Sans, Arial", size=13, color="#333"),
)
# --- Supprimer tous les axes (indices sur les côtés) ---
for axis in fig.layout:
if axis.startswith("xaxis") or axis.startswith("yaxis"):
fig.layout[axis].visible = False
# --- Légende élégante ---
top_partis = df_tetes_dep["liste_en_tete"].value_counts().head(5).index
for parti in top_partis:
fig.add_trace(go.Scatter(
x=[None], y=[None],
mode="markers",
marker=dict(size=11, color=COULEURS_LISTES.get(parti, "#BDBDBD")),
legendgroup=parti,
showlegend=True,
name=parti
))
fig.update_layout(
legend=dict(
title_text="<b>Listes électorales</b>",
orientation="h",
x=0.5,
xanchor="center",
y=0.90,
yanchor="bottom",
bgcolor="rgba(255,255,255,0.6)",
bordercolor="rgba(0,0,0,0.1)",
borderwidth=1,
itemsizing="constant",
font=dict(size=12),
title_font=dict(size=13)
)
)
# --- Titres des sous-graphiques rapprochés ---
for ann in fig['layout']['annotations']:
ann['y'] = 0.94
ann['font'] = dict(size=15, color="#222", family="Arial")
fig.show()
📊 Analyse des dynamiques électorales départementales — Élections européennes 2019 vs 2024
La comparaison des cartes départementales met en lumière une recomposition nette du paysage politique français entre 2019 et 2024. En 2019, la carte révèle une polarisation territoriale plus équilibrée entre les listes Renaissance (RE) et Rassemblement National (RN), avec une présence marquée de RE dans l’Ouest et le Sud-Ouest, et du RN dans le Nord, l’Est et le Sud-Est.
En revanche, en 2024, la situation évolue profondément : le RN s’impose massivement sur l’ensemble du territoire, remportant la quasi-totalité des départements métropolitains. Cette domination traduit une homogénéisation du vote protestataire à l’échelle nationale et un recul significatif des forces centristes et écologistes dans de nombreuses zones où elles étaient auparavant en tête.
- Recul géographique du camp présidentiel (RE) : autrefois dominant dans la façade atlantique et le Sud-Ouest, il ne conserve plus qu’une influence marginale.
- Consolidation du RN : progression quasi uniforme sur l’ensemble de la France métropolitaine, témoignant d’un élargissement sociologique et territorial.
- Faible percée des autres listes : les partis de gauche (LFI, PS) et écologistes n’arrivent en tête que dans quelques départements isolés.
Cette évolution illustre un glissement électoral vers un vote majoritairement unifié autour du RN, rompant avec les clivages géographiques observés lors du précédent scrutin. Elle traduit une nationalisation du vote et une concentration du leadership électoral autour d’un seul pôle politique à l’échelle départementale.
🗺️ Cartes des têtes de listes par département — Île-de-France (2019 vs 2024)
Cette visualisation compare la répartition des listes arrivées en tête dans les départements franciliens entre les élections européennes de 2019 et 2024. Elle met en évidence les dynamiques internes à la région capitale, marquées par de fortes disparités électorales entre le cœur urbain et la périphérie.
- Identifier la liste électorale majoritaire dans chaque département francilien.
- Comparer les changements de domination politique entre 2019 et 2024.
- Mettre en évidence la polarisation territoriale du vote francilien entre zones centrales et périphériques.
# --- Graphique : Listes arrivées en tête par département — Île-de-France ---
# --- Codes départements Île-de-France
idf_codes = ["75", "77", "78", "91", "92", "93", "94", "95"]
# --- Filtrage du GeoDataFrame départemental ---
gdf_idf = df_dep[df_dep["code_departement"].isin(idf_codes)].copy()
# --- Fusion des têtes de listes pour 2019 et 2024
df_idf_tetes = df_tetes_dep[df_tetes_dep["code_departement"].isin(idf_codes)].copy()
partis = list(COULEURS_LISTES.keys())
palette = [COULEURS_LISTES[p] for p in partis]
color_scale = [[i / (len(palette) - 1), palette[i]] for i in range(len(palette))]
# --- Préparation des GeoJSON par année ---
geojson_idf_2019 = json.loads(
gdf_idf.merge(
df_idf_tetes[df_idf_tetes["annee"] == "2019"]
.loc[:, ["nom_departement_2024", "liste_en_tete"]]
.drop_duplicates("nom_departement_2024"),
on="nom_departement_2024",
how="left",
).to_json()
)
geojson_idf_2024 = json.loads(
gdf_idf.merge(
df_idf_tetes[df_idf_tetes["annee"] == "2024"]
.loc[:, ["nom_departement_2024", "liste_en_tete"]]
.drop_duplicates("nom_departement_2024"),
on="nom_departement_2024",
how="left",
).to_json()
)
# --- Figure 1x2 ---
fig = make_subplots(
rows=1, cols=2,
subplot_titles=["2019", "2024"],
specs=[[{"type": "choropleth"}, {"type": "choropleth"}]],
)
# --- Carte 2019 ---
df_2019 = df_idf_tetes[df_idf_tetes["annee"] == "2019"]
z2019 = [partis.index(x) if x in partis else len(palette) - 1 for x in df_2019["liste_en_tete"]]
fig.add_trace(
go.Choropleth(
geojson=geojson_idf_2019,
locations=df_2019["nom_departement_2024"],
z=z2019,
featureidkey="properties.nom_departement_2024",
colorscale=color_scale,
zmin=0, zmax=len(palette) - 1,
showscale=False,
customdata=df_2019[["nom_departement_2024", "liste_en_tete"]],
hovertemplate="<b>%{customdata[0]}</b><br>Liste en tête : %{customdata[1]}<extra></extra>",
marker_line_color="white",
marker_line_width=0.6,
),
row=1, col=1,
)
# --- Carte 2024 ---
df_2024 = df_idf_tetes[df_idf_tetes["annee"] == "2024"]
z2024 = [partis.index(x) if x in partis else len(palette) - 1 for x in df_2024["liste_en_tete"]]
fig.add_trace(
go.Choropleth(
geojson=geojson_idf_2024,
locations=df_2024["nom_departement_2024"],
z=z2024,
featureidkey="properties.nom_departement_2024",
colorscale=color_scale,
zmin=0, zmax=len(palette) - 1,
showscale=False,
customdata=df_2024[["nom_departement_2024", "liste_en_tete"]],
hovertemplate="<b>%{customdata[0]}</b><br>Liste en tête : %{customdata[1]}<extra></extra>",
marker_line_color="white",
marker_line_width=0.6,
),
row=1, col=2,
)
# --- Positionnement géographique ---
fig.data[0].geo = "geo"
fig.data[1].geo = "geo2"
# --- Légende personnalisée ---
idf_partis = sorted(df_idf_tetes["liste_en_tete"].unique())
for parti in idf_partis:
fig.add_trace(go.Scatter(
x=[None], y=[None],
mode="markers",
marker=dict(size=10, color=COULEURS_LISTES.get(parti, "#BDBDBD")),
name=parti,
showlegend=True
))
# --- Mise en page esthétique ---
fig.update_layout(
template="plotly_white",
dragmode=False,
title=dict(
text="<b>Listes arrivées en tête par département — Île-de-France</b><br>"
"<span style='font-size:15px; color:#555;'>Comparaison des élections européennes 2019 et 2024</span>",
x=0.5,
y=0.90,
xanchor="center",
yanchor="top",
font=dict(size=20, family="Arial, Open Sans")
),
margin=dict(l=40, r=40, t=100, b=40),
geo=dict(
projection_type="mercator",
center=dict(lon=2.35, lat=48.85),
lonaxis_range=[1.5, 3.5],
lataxis_range=[48, 49.5],
showcoastlines=False,
showcountries=False,
showframe=False,
bgcolor="rgba(0,0,0,0)"
),
geo2=dict(
projection_type="mercator",
center=dict(lon=2.35, lat=48.85),
lonaxis_range=[1.5, 3.5],
lataxis_range=[48, 49.5],
showcoastlines=False,
showcountries=False,
showframe=False,
bgcolor="rgba(0,0,0,0)"
),
legend=dict(
title_text="<b>Listes électorales</b>",
orientation="v",
x=0.5,
xanchor="center",
y=0.87,
yanchor="middle",
bgcolor="rgba(255,255,255,0.7)",
bordercolor="rgba(0,0,0,0.15)",
borderwidth=1,
font=dict(size=12),
title_font=dict(size=13)
),
font=dict(family="Open Sans, Arial", size=13, color="#333"),
paper_bgcolor="#F9FAFB",
plot_bgcolor="#F9FAFB",
)
# --- Nettoyage visuel ---
fig.update_xaxes(visible=False)
fig.update_yaxes(visible=False)
# --- Sous-titres plus rapprochés ---
for ann in fig['layout']['annotations']:
ann['y'] = 0.93
ann['font'] = dict(size=15, color="#222", family="Arial")
fig.show()
Analyse du graphique : têtes de liste par département — Île-de-France (2019 vs 2024)
Les cartes comparent la répartition des listes arrivées en tête dans les départements d'Île-de-France entre 2019 et 2024, mettant en évidence une recomposition politique majeure dans la région capitale. L’évolution du vote illustre, à une échelle condensée, les dynamiques nationales de fragmentatioin électorale et sociale.
- 🟡 La quasi-totalité des départements place la liste Renaissance (RE) en tête (en jaune), témoignant d’un ancrage fort du vote macroniste dans la région parisienne.
- 🔵 Seul le Seine-et-Marne, plus rural et périphérique, accorde la première place au Rassemblement national (RN), premier signe d’un basculement territorial à venir.
- Ce résultat reflète la sociologie urbaine de l’Île-de-France, où les électeurs soutiennent majoritairement le projet centriste et pro-UE.
En 2024, le paysage d'Île-de-France se transforme radicalement :
- Le Rassemblement national (RN), en bleu, progresse massivement et arrive en tête dans la majorité des départements, notamment ceux de la grande couronne (Seine-et-Marne, Essonne, Val-d’Oise), confirmant son ancrage dans les zones périurbaines et populaires.
- Le centre parisien et la petite couronne se distinguent par une plus grande diversité politique :
- Paris place en tête le Parti socialiste (PS) (rose), symbole du retour d’un vote social-démocrate urbain et modéré.
- Le Seine-Saint-Denis vote majoritairement pour La France insoumise (LFI) (rouge), fidèle à son profil populaire et militant.
- Les Hauts-de-Seine et le Val-de-Marne conservent une présence relative de Renaissance, sans toutefois dominer la région.
- Les centres urbains denses (Paris, petite couronne) tendent vers la gauche progressiste et sociale, attachée à l’Union européenne et aux valeurs métropolitaines.
- La grande couronne, marquée par la précarité et l’éloignement des centres décisionnels, exprime un vote protestataire incarné par le RN.
- Cette opposition interne traduit un clivage socio-territorial fort entre le cœur métropolitain intégré et les périphéries en tension.
L’Île-de-France, autrefois bastion du vote centriste et pro-européen, devient en 2024 un véritable microcosme du clivage national :
- Une France des métropoles, tournée vers la gauche et l’Europe, concentrée dans le cœur urbain.
- Une France périphérique, socialement fragilisée, où le vote RN s’impose comme principal vecteur de contestation.
Cette polarisation interne à la région capitale illustre la reconfiguration du vote européen autour d’un nouvel axe : la tension entre intégration européenne et détachement socio-économique.
📘 Conclusion générale : une recomposition électorale et territoriale profonde entre 2019 et 2024
L’étude de l’évolution de la géographie électorale française entre les élections européennes de 2019 et 2024 met en évidence une double dynamique : politique et territoriale qui redéfinit durablement les équilibres électoraux du pays.
📊 Participation électorale : une remobilisation inégale
Les données révèlent une légère remobilisation électorale en 2024, mais cette progression reste territorialement contrastée :
- Les régions urbaines et métropolitaines enregistrent une participation supérieure à la moyenne, portée par des électeurs plus insérés dans les dynamiques économiques et européennes.
- À l’inverse, les territoires ruraux, périurbains et d'outre-mer demeurent marqués par une abstention structurelle élevée.
Cette géographie de la mobilisation traduit une fracture civique persistante, opposant les espaces connectés et intégrés à l’Europe à ceux qui s’en sentent exclus.
🗳️ Recomposition politique : domination du RN et recul du centre
Sur le plan politique, la comparaison entre 2019 et 2024 montre une restructuration profonde du paysage électoral :
- Le Rassemblement national (RN), déjà fort en 2019, devient en 2024 la première force politique nationale, dominant la quasi-totalité des régions françaises.
- Son implantation s’est étendue à des territoires auparavant modérés, notamment dans l’Ouest et le Centre, démontrant un vote de contestation généralisé.
- La majorité présidentielle (Renaissance) subit un recul net, perdant son statut de pivot politique et ses bastions métropolitains.
🔴 La gauche en recomposition : renouveau social-démocrate et ancrage populaire
- Le Parti socialiste (PS) retrouve une influence régionale, notamment à Paris, grâce à un positionnement social-démocrate clarifié.
- La France insoumise (LFI) s’impose dans les grands pôles urbains et populaires, ainsi que dans certains territoires d'outre-mer.
- Ces dynamiques traduisent un retour d'une division sociale et territoriale au cœur du vote européen.
🏙️ L’Île-de-France : miroir du clivage national
L’analyse spécifique de l’Île-de-France illustre parfaitement cette fracture :
- La capitale et la petite couronne s’ancrent à gauche, entre vote social-démocrate (PS) et militant (LFI).
- La grande couronne, plus populaire et éloignée du centre, se tourne vers le RN, traduisant un vote de rupture.
La région devient ainsi un microcosme du clivage national : métropoles progressistes contre périphéries contestataires.
⚖️ Une opposition marquée du paysage électoral
Entre 2019 et 2024, la France passe d’un système dominé par deux principales forces : la liste Renaissance et le Rassemblement national (RN), à une situation où le RN s’impose comme la première force dans la quasi-totalité des régions.
Toutefois, les grandes métropoles conservent un profil distinct : elles votent majoritairement à gauche, en faveur des listes socialistes, écologistes ou insoumises, tandis que les territoires périphériques et ruraux se tournent davantage vers le RN.
Cette évolution met en évidence une reconfiguration territoriale du vote : la France urbaine et connectée se distingue de la France périphérique, selon des dynamiques liées aux conditions socio-économiques et au rapport différencié au projet européen.